<script setup lang="ts">
import { Dialog, DialogDescription, DialogPanel, TransitionChild, TransitionRoot } from '@headlessui/vue';
import type { OverlayScrollbarsComponentRef } from 'overlayscrollbars-vue';
import { OverlayScrollbarsComponent } from 'overlayscrollbars-vue';
import { useI18n } from 'vue-i18n';
import { cloneDeep } from 'lodash-es';
import hotkeys from 'hotkeys-js';
import type { RouteRecordRaw } from 'vue-router';
import Breadcrumb from '../Breadcrumb/index.vue';
import BreadcrumbItem from '../Breadcrumb/item.vue';
import { resolveRoutePath } from '@/utils';
import eventBus from '@/utils/eventBus';
import { i18nTitleInjectionKey } from '@/utils/injectionKeys';
import useSettingsStore from '@/store/modules/settings';
import useRouteStore from '@/store/modules/route';
import useMenuStore from '@/store/modules/menu';
import useTabbarStore from '@/store/modules/tabbar';
import type { Tabbar } from '#/tabbar';
import type { Menu } from '#/menu';
import EpSearch from '~icons/ep/search';
import TablerMoodEmpty from '~icons/tabler/mood-empty';
import IonMdReturnLeft from '~icons/ion/md-return-left';
import AntDesignCaretUpFilled from '~icons/ant-design/caret-up-filled';
import AntDesignCaretDownFilled from '~icons/ant-design/caret-down-filled';

defineOptions({
  name: 'Search',
});

const overlayTransitionClass = ref({
  enter: 'ease-in-out duration-500',
  enterFrom: 'opacity-0',
  enterTo: 'opacity-100',
  leave: 'ease-in-out duration-500',
  leaveFrom: 'opacity-100',
  leaveTo: 'opacity-0',
});

const transitionClass = computed(() => {
  return {
    enter: 'ease-out duration-300',
    enterFrom: 'opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95',
    enterTo: 'opacity-100 translate-y-0 sm:scale-100',
    leave: 'ease-in duration-200',
    leaveFrom: 'opacity-100 translate-y-0 sm:scale-100',
    leaveTo: 'opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95',
  };
});

const { t } = useI18n();

const router = useRouter();
const settingsStore = useSettingsStore();
const routeStore = useRouteStore();
const menuStore = useMenuStore();
const tabbarStore = useTabbarStore();

type searchTypes = 'menu' | 'tab';
interface listTypes {
  path: string
  icon?: string
  title?: string | (() => string)
  i18n?: string
  link?: string
  breadcrumb: any[]
}

const generateI18nTitle = inject(i18nTitleInjectionKey, Function, true);
const isShow = ref(false);
const searchType = ref<searchTypes>();
const searchInput = ref('');
const sourceList = ref<listTypes[]>([]);
const actived = ref(-1);

const searchInputRef = ref();
const searchResultRef = ref<OverlayScrollbarsComponentRef>();
const searchResultItemRef = ref<HTMLElement[]>([]);
onBeforeUpdate(() => {
  searchResultItemRef.value = [];
});

const resultList = computed(() => {
  let result = [];
  result = sourceList.value.filter((item) => {
    let flag = false;
    if (generateI18nTitle(item.i18n, item.title).toString().includes(searchInput.value)) {
      flag = true;
    }
    if (item.path.includes(searchInput.value)) {
      flag = true;
    }
    if (item.breadcrumb.some((b: { i18n: any, title: any }) => generateI18nTitle(b.i18n, b.title).toString().includes(searchInput.value))) {
      flag = true;
    }
    return flag;
  });
  return result;
});

watch(() => isShow.value, (val) => {
  if (val) {
    searchInput.value = '';
    actived.value = -1;
    // 当搜索显示的时候绑定上、下、回车快捷键，隐藏的时候再解绑。另外当 input 处于 focus 状态时，采用 vue 来绑定键盘事件
    hotkeys('up', keyUp);
    hotkeys('down', keyDown);
    hotkeys('enter', keyEnter);
  }
  else {
    hotkeys.unbind('up', keyUp);
    hotkeys.unbind('down', keyDown);
    hotkeys.unbind('enter', keyEnter);
  }
});
watch(() => resultList.value, () => {
  actived.value = -1;
  handleScroll();
});

onMounted(() => {
  eventBus.on('global-search-toggle', (type) => {
    if (!isShow.value) {
      initSourceList(type);
    }
    isShow.value = !isShow.value;
  });
  hotkeys('alt+s', (e) => {
    if (settingsStore.settings.navSearch.enable && settingsStore.settings.navSearch.enableHotkeys) {
      e.preventDefault();
      initSourceList();
      isShow.value = true;
    }
  });
  hotkeys('esc', (e) => {
    if (settingsStore.settings.navSearch.enable && settingsStore.settings.navSearch.enableHotkeys) {
      e.preventDefault();
      isShow.value = false;
    }
  });
  initSourceList();
});

function switchType(type: any) {
  searchInputRef.value.focus();
  initSourceList(type);
}

function initSourceList(type: searchTypes = 'menu') {
  searchType.value = type;
  sourceList.value = [];
  switch (type) {
    case 'menu':
      if (settingsStore.settings.app.routeBaseOn !== 'filesystem') {
        routeStore.routes.forEach((item) => {
          item.children && getSourceList(item.children as RouteRecordRaw[]);
        });
      }
      else {
        menuStore.menus.forEach((item) => {
          getSourceListByMenus(item.children);
        });
      }
      break;
    case 'tab':
      getSourceListByTabs(tabbarStore.list);
      break;
  }
}

function hasChildren(item: RouteRecordRaw) {
  let flag = true;
  if (item.children?.every(i => i.meta?.sidebar === false)) {
    flag = false;
  }
  return flag;
}
function getSourceList(arr: RouteRecordRaw[], basePath?: string, icon?: string, breadcrumb?: { title?: string | (() => string), i18n?: string }[]) {
  arr.forEach((item) => {
    if (item.meta?.sidebar !== false) {
      const breadcrumbTemp = cloneDeep(breadcrumb) || [];
      if (item.children && hasChildren(item)) {
        breadcrumbTemp.push({
          title: item.meta?.title,
          i18n: item.meta?.i18n,
        });
        getSourceList(item.children, resolveRoutePath(basePath, item.path), item.meta?.icon ?? icon, breadcrumbTemp);
      }
      else {
        breadcrumbTemp.push({
          title: item.meta?.title,
          i18n: item.meta?.i18n,
        });
        sourceList.value.push({
          path: resolveRoutePath(basePath, item.path),
          icon: item.meta?.icon ?? icon,
          title: item.meta?.title,
          i18n: item.meta?.i18n,
          link: item.meta?.link,
          breadcrumb: breadcrumbTemp,
        });
      }
    }
  });
}
function getSourceListByMenus(arr: Menu.recordRaw[], icon?: string, breadcrumb?: { title?: string | (() => string), i18n?: string }[]) {
  arr.forEach((item) => {
    const breadcrumbTemp = cloneDeep(breadcrumb) || [];
    if (item.children && item.children.length > 0) {
      breadcrumbTemp.push({
        title: item.meta?.title,
        i18n: item.meta?.i18n,
      });
      getSourceListByMenus(item.children, item.meta?.icon ?? icon, breadcrumbTemp);
    }
    else {
      breadcrumbTemp.push({
        title: item.meta?.title,
        i18n: item.meta?.i18n,
      });
      sourceList.value.push({
        icon: item.meta?.icon ?? icon,
        title: item.meta?.title,
        path: item.path as string,
        i18n: item.meta?.i18n,
        breadcrumb: breadcrumbTemp,
      });
    }
  });
}
function getSourceListByTabs(arr: Tabbar.recordRaw[]) {
  arr.forEach((item) => {
    sourceList.value.push({
      icon: item.icon,
      title: item.title,
      path: item.fullPath,
      i18n: item.i18n,
      breadcrumb: [],
    });
  });
}

function keyUp() {
  if (resultList.value.length) {
    actived.value -= 1;
    if (actived.value < 0) {
      actived.value = resultList.value.length - 1;
    }
    handleScroll();
  }
}
function keyDown() {
  if (resultList.value.length) {
    actived.value += 1;
    if (actived.value > resultList.value.length - 1) {
      actived.value = 0;
    }
    handleScroll();
  }
}
function keyEnter() {
  if (actived.value !== -1) {
    searchResultItemRef.value.find(item => Number.parseInt(item.dataset.index!) === actived.value)?.click();
  }
}
function handleScroll() {
  if (searchResultRef.value) {
    const contentDom = searchResultRef.value.osInstance()!.elements().content;
    let scrollTo = 0;
    if (actived.value !== -1) {
      scrollTo = contentDom.scrollTop;
      const activedOffsetTop = searchResultItemRef.value.find(item => Number.parseInt(item.dataset.index!) === actived.value)?.offsetTop ?? 0;
      const activedClientHeight = searchResultItemRef.value.find(item => Number.parseInt(item.dataset.index!) === actived.value)?.clientHeight ?? 0;
      const searchScrollTop = contentDom.scrollTop;
      const searchClientHeight = contentDom.clientHeight;
      if (activedOffsetTop + activedClientHeight > searchScrollTop + searchClientHeight) {
        scrollTo = activedOffsetTop + activedClientHeight - searchClientHeight;
      }
      else if (activedOffsetTop <= searchScrollTop) {
        scrollTo = activedOffsetTop;
      }
    }
    contentDom.scrollTo({
      top: scrollTo,
    });
  }
}

function pageJump(path: listTypes['path'], link: listTypes['link']) {
  if (link) {
    window.open(link, '_blank');
  }
  else {
    router.push(path);
  }
  isShow.value = false;
}
</script>

<template>
  <TransitionRoot as="template" :show="isShow">
    <Dialog :initial-focus="searchInputRef" class="fixed inset-0 z-2000 flex" @close="isShow && eventBus.emit('global-search-toggle')">
      <TransitionChild as="template" v-bind="overlayTransitionClass">
        <div class="fixed inset-0 bg-stone-200/75 backdrop-blur-sm transition-opacity dark:bg-stone-8/75" />
      </TransitionChild>
      <div class="fixed inset-0">
        <div class="h-full flex items-end justify-center p-4 text-center sm:items-center sm:p-0">
          <TransitionChild as="template" v-bind="transitionClass">
            <DialogPanel class="relative h-full max-h-4/5 w-full flex flex-col text-left sm:max-w-2xl">
              <HTabList
                v-if="settingsStore.settings.tabbar.enable"
                v-model="searchType"
                :options="[
                  { label: t('app.search.type.menu'), value: 'menu' },
                  { label: t('app.search.type.tab'), value: 'tab' },
                ]"
                class="mb-4 flex!"
                @click.stop
                @change="switchType"
              />
              <div class="flex flex-col overflow-y-auto rounded-xl bg-white shadow-xl dark:bg-stone-8">
                <div class="flex items-center px-4 py-3" border-b="~ solid stone-2 dark:stone-7">
                  <EpSearch text="18px" class="text-stone-5" />
                  <input
                    ref="searchInputRef"
                    v-model="searchInput"
                    :placeholder="t('app.search.input')"
                    class="w-full border-0 rounded-md bg-transparent px-3 text-base text-dark dark:text-white focus:outline-none placeholder-stone-4 dark:placeholder-stone-5"
                    @keydown.esc="eventBus.emit('global-search-toggle')"
                    @keydown.up.prevent="keyUp"
                    @keydown.down.prevent="keyDown"
                    @keydown.enter.prevent="keyEnter"
                  >
                </div>
                <DialogDescription class="relative m-0 of-y-hidden">
                  <OverlayScrollbarsComponent
                    ref="searchResultRef"
                    :options="{ scrollbars: { autoHide: 'leave', autoHideDelay: 300 } }"
                    defer
                    class="h-full"
                  >
                    <template v-if="resultList.length > 0">
                      <a
                        v-for="(item, index) in resultList"
                        ref="searchResultItemRef"
                        :key="item.path"
                        class="flex cursor-pointer items-center"
                        :class="{ 'bg-stone-2/40 dark:bg-stone-7/40': index === actived }"
                        :data-index="index"
                        @click="pageJump(item.path, item.link)"
                        @mouseover="actived = index"
                      >
                        <PubSvgIcon
                          v-if="item.icon"
                          :name="item.icon"
                          :size="20"
                          class="basis-16 transition"
                          :class="{ 'scale-120 text-ui-primary': index === actived }"
                        />
                        <div class="flex flex-1 flex-col gap-1 truncate px-4 py-3" border-l="~ solid stone-2 dark:stone-7">
                          <div class="truncate text-base font-bold">{{ generateI18nTitle(item.i18n, item.title) }}</div>
                          <Breadcrumb v-if="item.breadcrumb.length" class="truncate">
                            <BreadcrumbItem v-for="(bc, bcIndex) in item.breadcrumb" :key="bcIndex" class="text-xs">
                              {{ generateI18nTitle(bc.i18n, bc.title) }}
                            </BreadcrumbItem>
                          </Breadcrumb>
                        </div>
                      </a>
                    </template>
                    <template v-else>
                      <div flex="center col" py-6 text-stone-5>
                        <TablerMoodEmpty text="40px" />
                        <p m-2 text-base>
                          没有找到你想要的
                        </p>
                      </div>
                    </template>
                  </OverlayScrollbarsComponent>
                </DialogDescription>
                <div v-if="settingsStore.mode === 'pc'" class="flex justify-between px-4 py-3" border-t="~ solid stone-2 dark:stone-7">
                  <div class="flex gap-8">
                    <div class="inline-flex items-center gap-1 text-xs">
                      <HKbd>
                        <IonMdReturnLeft text="14px" />
                      </HKbd>
                      <span>{{ t('app.search.enter') }}</span>
                    </div>
                    <div class="inline-flex items-center gap-1 text-xs">
                      <HKbd>
                        <AntDesignCaretUpFilled text="14px" />
                      </HKbd>
                      <HKbd>
                        <AntDesignCaretDownFilled text="14px" />
                      </HKbd>
                      <span>{{ t('app.search.up_down') }}</span>
                    </div>
                  </div>
                  <div v-if="settingsStore.settings.navSearch.enableHotkeys" class="inline-flex items-center gap-1 text-xs">
                    <HKbd>
                      ESC
                    </HKbd>
                    <span>{{ t('app.search.esc') }}</span>
                  </div>
                </div>
              </div>
            </DialogPanel>
          </TransitionChild>
        </div>
      </div>
    </Dialog>
  </TransitionRoot>
</template>
